/********************************************************************************
 *    Copyright (C) 2014 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH    *
 *                                                                              *
 *              This software is distributed under the terms of the             *
 *              GNU Lesser General Public Licence (LGPL) version 3,             *
 *                  copied verbatim in the file "LICENSE"                       *
 ********************************************************************************/
#include "HiNewDetector.h"

#include "FairGeoInterface.h"
#include "FairGeoLoader.h"
#include "FairGeoNode.h"
#include "FairGeoVolume.h"
#include "FairRootManager.h"
#include "FairRun.h"
#include "FairRuntimeDb.h"
#include "FairVolume.h"
#include "HiStack.h"
#include "HiNewDetectorGeo.h"
#include "HiNewDetectorGeoPar.h"
#include "HiNewDetectorPoint.h"

#include <TClonesArray.h>
#include <TGeoBBox.h>
#include <TGeoCompositeShape.h>
#include <TGeoManager.h>
#include <TGeoMaterial.h>
#include <TGeoMedium.h>
#include <TGeoTube.h>
#include <TVirtualMC.h>
#include <iostream>
using std::cout;
using std::endl;

HiNewDetector::HiNewDetector()
    : FairDetector("HiNewDetector", kTRUE, kHiNewDetector)
    , fTrackID(-1)
    , fVolumeID(-1)
    , fPos()
    , fMom()
    , fTime(-1.)
    , fLength(-1.)
    , fELoss(-1)
    , fDetectorName("HiNewDetector")
    , fPointName("HiNewDetectorPoint")
    , fDetectorId(kHiNewDetector)
    , fHiNewDetectorPointCollection(new TClonesArray("HiNewDetectorPoint"))
{}

HiNewDetector::HiNewDetector(const char* DetectorName, Bool_t active, const char* PointName, DetectorId detId)
    : FairDetector(DetectorName, active, detId)
    , fTrackID(-1)
    , fVolumeID(-1)
    , fPos()
    , fMom()
    , fTime(-1.)
    , fLength(-1.)
    , fELoss(-1)
    , fDetectorName(DetectorName)
    , fPointName(PointName)
    , fDetectorId(detId)
    , fHiNewDetectorPointCollection(new TClonesArray("HiNewDetectorPoint"))
{}

HiNewDetector::HiNewDetector(const HiNewDetector& right)
    : FairDetector(right)
    , fTrackID(-1)
    , fVolumeID(-1)
    , fPos()
    , fMom()
    , fTime(-1.)
    , fLength(-1.)
    , fDetectorName(right.fDetectorName)
    , fPointName(right.fPointName)
    , fDetectorId(right.fDetectorId)
    , fHiNewDetectorPointCollection(new TClonesArray("HiNewDetectorPoint"))
{}

HiNewDetector::~HiNewDetector()
{
    if (fHiNewDetectorPointCollection) {
        fHiNewDetectorPointCollection->Delete();
        delete fHiNewDetectorPointCollection;
    }
}

void HiNewDetector::Initialize()
{
    /**
     * WORKAROUND needed for Geant4 in MT mode
     * Call AddSensitiveVolume for sensitive volumes in order to fill
     * thread-local FairModule::svList.
     */
    DefineSensitiveVolumes();
    FairDetector::Initialize();
    FairRuntimeDb* rtdb = FairRun::Instance()->GetRuntimeDb();
    auto par = (HiNewDetectorGeoPar*)(rtdb->getContainer("HiNewDetectorGeoPar"));
    (void)par;   // stop warning about unused variable
}

Bool_t HiNewDetector::ProcessHits(FairVolume* vol)
{
    /** This method is called from the MC stepping */
    
    // Set parameters at entrance of volume. Reset ELoss.
    if (TVirtualMC::GetMC()->IsTrackEntering()) {
        fELoss = 0.;
        fTime = TVirtualMC::GetMC()->TrackTime() * 1.0e09;
        fLength = TVirtualMC::GetMC()->TrackLength();
        TVirtualMC::GetMC()->TrackPosition(fPos);
        TVirtualMC::GetMC()->TrackMomentum(fMom);
    }

    // Sum energy loss for all steps in the active volume
    fELoss += TVirtualMC::GetMC()->Edep();
    // Create HiNewDetectorPoint at exit of active volume
    if (TVirtualMC::GetMC()->IsTrackExiting() || TVirtualMC::GetMC()->IsTrackStop()
        || TVirtualMC::GetMC()->IsTrackDisappeared()) {
        fTrackID = TVirtualMC::GetMC()->GetStack()->GetCurrentTrackNumber();
        fVolumeID = vol->getMCid();
        if (fELoss == 0.) {
            return kFALSE;
        }
        AddHit(fTrackID,
               fVolumeID,
               TVector3(fPos.X(), fPos.Y(), fPos.Z()),
               TVector3(fMom.Px(), fMom.Py(), fMom.Pz()),
               fTime,
               fLength,
               fELoss);

        // Increment number of HiNewDetector det points in TParticle
        HiStack* stack = (HiStack*)TVirtualMC::GetMC()->GetStack();
        stack->AddPoint(fDetectorId);
    }

    return kTRUE;
}

void HiNewDetector::EndOfEvent()
{

    LOG(info) << fDetectorName << ": " << fHiNewDetectorPointCollection->GetEntriesFast() << " points registered in this event";

    fHiNewDetectorPointCollection->Clear();
}

void HiNewDetector::Register()
{

    /** This will create a branch in the output tree called
      HiNewDetectorPoint, setting the last parameter to kFALSE means:
      this collection will not be written to the file, it will exist
      only during the simulation.
  */

    if (!gMC->IsMT()) {
        FairRootManager::Instance()->Register(fPointName, fDetectorName, fHiNewDetectorPointCollection, kTRUE);
    } else {
        FairRootManager::Instance()->RegisterAny(fPointName, fHiNewDetectorPointCollection, kTRUE);
    }
    LOG(info) << "Registering branch: " << fPointName
          << " (" << fHiNewDetectorPointCollection << ")";
}

TClonesArray* HiNewDetector::GetCollection(Int_t iColl) const
{
    if (iColl == 0) {
        return fHiNewDetectorPointCollection;
    } else {
        return NULL;
    }
}

void HiNewDetector::Reset() { fHiNewDetectorPointCollection->Clear(); }

void HiNewDetector::ConstructGeometry()
{
    TString fileName = GetGeometryFileName();
    if (fileName.EndsWith(".root")) {
	    LOG(info)<<"Constructing "<< fDetectorName <<" geometry from ROOT file "<<fileName;
	    ConstructRootGeometry();
    } else {
	    LOG(fatal) << "Geometry file is not specified.";
        exit(1);
    }
}

HiNewDetectorPoint* HiNewDetector::AddHit(Int_t trackID,
                                      Int_t detID,
                                      TVector3 pos,
                                      TVector3 mom,
                                      Double_t time,
                                      Double_t length,
                                      Double_t eLoss)
{
    TClonesArray& clref = *fHiNewDetectorPointCollection;
    Int_t size = clref.GetEntriesFast();
    return new (clref[size]) HiNewDetectorPoint(trackID, detID, pos, mom, time, length, eLoss);
}

FairModule* HiNewDetector::CloneModule() const { return new HiNewDetector(*this); }

void HiNewDetector::DefineSensitiveVolumes()
{
    TObjArray* volumes = gGeoManager->GetListOfVolumes();
    TIter next(volumes);
    TGeoVolume* volume;
    while ((volume = static_cast<TGeoVolume*>(next()))) {
        if (IsSensitive(volume->GetName())) {
            LOG(debug2) << "Sensitive Volume " << volume->GetName();
            AddSensitiveVolume(volume);
        }
    }
}

Bool_t HiNewDetector::IsSensitive(const std::string& name)
{
    // Create a copy of the name to avoid modifying the original
    std::string detectorNameLower = fDetectorName.Data();
    detectorNameLower = detectorNameLower.substr(2); // Remove "Hi" prefix
    std::string nameLower = name;

    // Convert both strings to lowercase
    std::transform(detectorNameLower.begin(), detectorNameLower.end(), detectorNameLower.begin(), ::tolower);
    std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), ::tolower);

    if (nameLower.find(detectorNameLower) != std::string::npos && nameLower.find("sensitive") != std::string::npos) {
        return kTRUE;
    }
    return kFALSE;
}

ClassImp(HiNewDetector);
